3 RANKS = %w(A 2 3 4 5 6 7 8 9 10 J Q K)
9 def Card.value_to_chr(value)
14 def Card.chr_to_value(chr)
20 def initialize(rank, suit)
26 @value = (SUITS.index(suit) * 13) + RANKS.index(rank) + 1
31 # return @value.to_s if @value != JOKER_VALUE
33 "#{@rank}#{@suit} #{@value.to_s}"
41 Card.value_to_chr(@value)
50 RANKS.each { | rank | @cards << Card.new(rank, suit) }
52 @joker_a = Card.new(JOKER_RANK, 'A')
54 @joker_b = Card.new(JOKER_RANK, 'B')
58 # Keys the deck and returns itself.
60 # do nothing; keyed when initialized
64 # Return the next kestream value as a number (not a string).
65 # Keep going until we have a non-joker value.
68 until val != JOKER_VALUE
69 val = generate_next_keystream_value
74 # Return the next keystream value as a number 1-26 (not a string).
75 def generate_next_keystream_value
80 return output_number()
83 # Move a card a certain distance. Wrap around the end of the deck.
84 def move(card, distance)
85 old_pos = @cards.index(card)
86 new_pos = old_pos + distance
87 new_pos -= (@cards.length-1) if new_pos >= @cards.length
88 @cards[old_pos,1] = []
89 @cards[new_pos,0] = [card]
92 # Perform a triple cut around the two jokers. All cards above the top
93 # joker move to below the bottom joker and vice versa. The jokers and the
94 # cards between them do not move.
96 i = @cards.index(@joker_a)
97 j = @cards.index(@joker_b)
98 j, i = i, j if j < i # make sure i < j
99 @cards = slice(j+1, -1) + slice(i, j) + slice(0, i-1)
102 # Perform a count cut using the value of the bottom card. Cut the bottom
103 # card's value in cards off the top of the deck and reinsert them just
104 # above the bottom card.
106 i = @cards[@cards.length - 1].to_i
107 @cards = slice(i, -2) + slice(0, i-1) + [@cards[@cards.length-1]]
110 # Returns a non-nil cut of cards from the deck.
112 slice = @cards[from..to]
116 # Return the output number (not letter). Convert the top card to it's
117 # value and count down that many cards from the top of the deck, with the
118 # top card itself being card number one. Look at the card immediately
119 # after your count and convert it to a letter. This is the next letter in
120 # the keystream. If the output card is a joker, no letter is generated
121 # this sequence. This step does not alter the deck.
124 i -= @cards.length if i >= @cards.length
126 num -= 26 if num > 26
142 @deck = @keyed_deck.dup
144 str.split(//).each { | c |
150 msg_num = Card.chr_to_value(c)
151 key = @deck.next_keystream
153 diff += 26 if diff < 1
154 answer << Card.value_to_chr(diff)
160 @deck = @keyed_deck.dup
162 str.split(//).each { | c |
168 msg_num = Card.chr_to_value(c)
169 key = @deck.next_keystream
171 sum -= 26 if sum > 26
172 answer << Card.value_to_chr(sum)
178 @deck = @keyed_deck.dup
179 str.split(//).each { | c | yield c }
185 str = str.upcase.gsub(/[^A-Z]/, '')
192 last_len = words[words.length-1].length
193 words[words.length-1] += ('X' * (5 - last_len)) if last_len < 5
199 puts CryptKeeper.new(Deck.new.key).decrypt(prep_arg(ARGV[0]))
201 puts CryptKeeper.new(Deck.new.key).decrypt('CLEPK HHNIY CFPWH FDFEH')
202 puts CryptKeeper.new(Deck.new.key).decrypt('ABVAW LWZSY OORYK DUPVH')